package com.bang.study.day01;

import com.bang.study.util.ArrayUtil;
import com.bang.study.util.TestUtil;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

/**
 * @Auther: Bang
 * @Date: 2019/9/21 0021 10:18
 * @Description: 插入排序
 * @描述: 一般来说，插入排序都采用in-place在数组上实现。具体算法描述如下：
 * 1. 从第一个元素开始，该元素可以认为已经被排序；
 * 2. 取出下一个元素，在已经排序的元素序列中从后向前扫描；
 * 3. 如果该元素（已排序）大于新元素，将该元素移到下一位置；
 * 4. 重复步骤3，直到找到已排序的元素小于或者等于新元素的位置；
 * 5. 将新元素插入到该位置后；
 * 6. 重复步骤2~5。
 * @算法分析: 最佳情况：T(n) = O(n)   最差情况：T(n) = O(n2)   平均情况：T(n) = O(n2)
 * 对于近乎有序的序列,插入排序的性能比其他排序算法更优
 * @工作原理: 通过构建有序序列，对于未排序数据，在已排序序列中从后向前扫描，找到相应位置并插入。
 * 插入排序在实现上，通常采用in-place排序（即只需用到O(1)的额外空间的排序），
 * 因而在从后向前扫描过程中，需要反复把已排序元素逐步向后挪位，为最新元素提供插入空间。
 */
public class InsertionSort {

    /**
     * 对数组进行排序
     *
     * @param arr
     */
    public static void sort(Integer[] arr) {
        int length = arr.length;
        // 从索引1开始 默认索引0是有序的
        for (int i = 1; i < length; i++) {
            // 当前需要和i之前数组比较的值
            int curr = arr[i];
            // 有序数组最后一个元素的索引j=i-1
            int j = i - 1;
            //  有序数组从最后一个元素开始和curr进行比较,直到找到小于等于curr的元素
            while (j >= 0 && arr[j] > curr) {
                arr[j + 1] = arr[j];
                j--;
            }
            // 将curr放到找到的元素的下一个位置
            arr[j + 1] = curr;
        }
    }



    public static void sortFor(Integer[] arr) {
        long startTime = System.nanoTime();
        int length = arr.length;
        // 从索引1开始 默认索引0是有序的
        for (int i = 1; i < length; i++) {
            // 当前需要和i之前数组比较的值
            int curr = arr[i];
            // j为curr应该插入的位置
            int j;
            //  有序数组从最后一个元素开始和curr进行比较,如果大于curr则将该元素赋值给后一位元素
            for (j = i; j > 0 && arr[j - 1] > curr; j--) {
                arr[j] = arr[j - 1];
            }
            // 循环终止 代表找到了 j应该插入的位置
            arr[j] = curr;
        }
        long endTime = System.nanoTime();
        double time = (endTime - startTime) / 1000000000.0;
        System.out.println(String.format("%s 消耗时间: %s S ,数据量: %s", "InsertionSort", time, arr.length));
        System.out.println();
    }


    /**
     * 对arr[left,right] 进行插入排序
     *
     * @param arr
     * @param left
     * @param right
     */
    public static void insertSort(Integer[] arr, int left, int right) {
        // 从索引1开始 默认索引0是有序的
        for (int i = left + 1; i <= right; i++) {
            // 当前需要和i之前数组比较的值
            int curr = arr[i];
            // j为curr应该插入的位置
            int j;
            //  有序数组从最后一个元素开始和curr进行比较,如果大于curr则将该元素赋值给后一位元素
            for (j = i; j > left && arr[j - 1] > curr; j--) {
                arr[j] = arr[j - 1];
            }
            // 循环终止 代表找到了 j应该插入的位置
            arr[j] = curr;
        }
    }

    public static void main(String[] args) throws Throwable {
        Integer[] arr = ArrayUtil.generateRandomArray(1000, 0, 10000);
        ArrayUtil.printArray(arr, 10);
        TestUtil.test(InsertionSort.class, arr);
        ArrayUtil.printArray(arr, 10);
        System.out.println("............................");

        MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
        MethodType mt = MethodType.methodType(void.class, Integer[].class);
        MethodHandle handle = publicLookup.findStatic(InsertionSort.class, "sortFor", mt);
        long startTime = System.nanoTime();
        handle.invokeWithArguments((Object) arr);
        long endTime = System.nanoTime();
        double time = (endTime - startTime) / 1000000000.0;
        System.err.println(String.format("%s 消耗时间: %s S ,数据量: %s", "InsertionSort", time, arr.length));
    }
}
